package com.gitee.xmhzzz.component.spring.cloud.component.dao.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Value;

import java.sql.Statement;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.*;
import java.util.regex.Matcher;

/**
 * @ClassName SqlCheckMybatisInterceptor
 * @Description 检测
 * @Author wzq
 * @Date 2023/9/24 21:45
 * @Version 1.0
 */
@Slf4j
@Intercepts({@Signature(
        type = StatementHandler.class,
        method = "query",
        args = {Statement.class, ResultHandler.class}
)})
public class SqlCheckMybatisInterceptor implements Interceptor {
    ThreadPoolExecutor checkSqlExecutor;

    private SqlCheckInterceptor sqlCheckInterceptor;

    @Value("${xmhzzz.mybatis.check.rows:1000}")
    private Long checkRows;

    @Value("${xmhzzz.mybatis.check.limit:1000}")
    private Long checkLimit;

    @Value("${xmhzzz.mybatis.check.enable:false}")
    private Boolean enable;

    public SqlCheckMybatisInterceptor(SqlCheckInterceptor sqlCheckInterceptor) {
        this.sqlCheckInterceptor = sqlCheckInterceptor;
        this.checkSqlExecutor = new ThreadPoolExecutor(1, 1, 5L, TimeUnit.SECONDS, new ArrayBlockingQueue(500), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());
    }

    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();
        int numRows;
        if (List.class.isAssignableFrom(result.getClass())) {
            numRows = ((List)result).size();
        } else {
            numRows = 0;
        }

        if (this.enable) {
            CompletableFuture.runAsync(() -> {
                StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
                MetaObject metaObject = MetaObject.forObject(statementHandler, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
                this.checkSql(metaObject, numRows);
            }, this.checkSqlExecutor);
        }

        return result;
    }

    private void checkSql(MetaObject metaObject, int numRows) {
        try {
            BoundSql boundSql = (BoundSql)metaObject.getValue("delegate.boundSql");
            Object parameterObject = boundSql.getParameterObject();
            String sql = boundSql.getSql().toLowerCase();
            MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
            Configuration configuration = mappedStatement.getConfiguration();
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();

            String parameterString;
            for(Iterator var10 = parameterMappings.iterator(); var10.hasNext(); sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(parameterString))) {
                ParameterMapping parameterMapping = (ParameterMapping)var10.next();
                String property = parameterMapping.getProperty();
                Object parameterValue;
                if (boundSql.hasAdditionalParameter(property)) {
                    parameterValue = boundSql.getAdditionalParameter(property);
                } else if (parameterObject == null) {
                    parameterValue = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    parameterValue = parameterObject;
                } else {
                    metaObject = configuration.newMetaObject(parameterObject);
                    parameterValue = metaObject.getValue(property);
                }

                parameterString = this.getParameterString(parameterValue);
            }

            this.sqlCheckInterceptor.beforePrepare(sql, this.checkLimit, numRows, this.checkRows);
        } catch (Exception var15) {
            log.error("check sql sync error", var15);
        }

    }

    private String getParameterString(Object parameterValue) {
        if (parameterValue == null) {
            return "NULL";
        } else {
            return parameterValue instanceof String ? "'" + parameterValue + "'" : parameterValue.toString();
        }
    }

    public void setProperties(Properties properties) {
    }
}
